﻿// (c) Copyright Michael Antonio.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved
using MichMan.Utilities;
using MichMan.Utilities.Requests;
using System.Linq;
using System.Xml.Linq;
using System;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Diagnostics;
using System.Runtime.Serialization;

namespace Subsonic.Data
{
    public class SubsonicRequestContext
    {
        private SubsonicRequestContext()
        {
            Client = "WindowsPhone7";
            Version = "1.2.0";
            Settings = SubsonicSettings.Instance;
        }

        public static SubsonicRequestContext Instance
        {
            get
            {
                return Singleton<SubsonicRequestContext>.Create(() => new SubsonicRequestContext());
            }
        }

        public SubsonicSettings Settings { get; set; }
        public SubsonicServer Server { get { return Settings.ActiveServer; } }

        public string Client { get; set; }
        public string Version { get; set; }

        #region SubsonicUrls
        private static string StdQueryString
        {
            get
            {
                return String.Format("v={0}&c={1}&u={2}&p={3}", Instance.Version, Instance.Client, Instance.Server.Username, Instance.Server.Password);
            }
        }

        public static string DownloadUrl(string id)
        {
            return String.Format("{0}/rest/download.view?{1}&id={2}&", Instance.Server.ServerAddress, StdQueryString, id);
        }

        public static string StreamUrl(string id)
        {
            // TODO:  Read the NetworkInterface class and settings to decide if we need to pass MaxBitRate.
            return String.Format("{0}/rest/stream.view?{1}&id={2}", Instance.Server.ServerAddress, StdQueryString, id);
        }

        // private static readonly int[] bitrates = new[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 };

        public static string StreamUrl(string id, int maxBitRate)
        {
            /*
            if (!bitrates.Contains(maxBitRate))
            {
                throw new ArgumentOutOfRangeException("maxBitRate", "Legal values are: 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320.");
            }
            */

            return String.Format("{0}/rest/stream.view?{1}&id={2}&maxBitRate={3}", Instance.Server.ServerAddress, StdQueryString, id, maxBitRate.ToString(), StdQueryString);
        }

        public static string CoverArtUrl(string id)
        {
            if (String.IsNullOrEmpty(id))
            {
                return "/Images/unknown_album.png";
            }
            return String.Format("{0}/rest/getCoverArt.view?{1}&id={2}", Instance.Server.ServerAddress, StdQueryString, id);
        }

        public static string CoverArtUrl(string id, string size)
        {
            if (String.IsNullOrEmpty(id))
            {
                return "/Images/unknown_album.png";
            }
            return String.Format("{0}/rest/getCoverArt.view?{1}&id={2}&size={3}", Instance.Server.ServerAddress, StdQueryString, id, size);
        }
        #endregion

    }

    public class SubsonicException : Exception
    {
        public SubsonicException(int code, string message)
            : base(message)
        {
            Code = code;
        }

        public int Code { get; set; }
    }

    public class SubsonicCoverArtRequest : SubsonicBaseRequest<MemoryStream>
    {
        public SubsonicCoverArtRequest()
        {
            Path = "getCoverArt.view";
        }

        [QueryParam("id", Required = true)]
        public string Id { get; set; }

        protected override void ProcessResult(Stream stream)
        {
            MemoryStream response = new MemoryStream();

            int b;
            while ((b = stream.ReadByte()) != -1)
            {
                response.WriteByte((byte)b);
            }
            Response = response;
        }
    }

    public class SubsonicRequest : SubsonicBaseRequest<SubsonicResponse>
    {
        protected override void OnRunRequestCompleted(RunRequestCompletedEventArgs runRequestCompletedEventArgs)
        {
            if (this.Response != null && this.Response.Error != null)
            {
                Error = new SubsonicException(this.Response.Error.Code, this.Response.Error.Message);
            }

            base.OnRunRequestCompleted(runRequestCompletedEventArgs);
        }
    }

    [UriFormat("{host}/rest/{restMethod}")]
    public class SubsonicBaseRequest<T> : ZPathDataRequest<T> where T : class, new()
    {
        public SubsonicBaseRequest()
        {
            Version = SubsonicRequestContext.Instance.Version;
            Client = SubsonicRequestContext.Instance.Client;
            Server = SubsonicRequestContext.Instance.Server;
        }

        public SubsonicServer Server { get; set; }
        [UriParam("host")]
        public string Host { get { return Server.ServerAddress; } }

        [UriParam("restMethod")]
        public string Path { get; set; }

        [QueryParam("v", Order = 0)]
        public string Version { get; set; }
        [QueryParam("c", Order = 1)]
        public string Client { get; set; }

        public override void RunRequestAsync()
        {
            if (SubsonicSettings.Instance.PlayMode == PlayMode.Offline)
            {
                // TODO:  Fulfill requests from isolated storage.
                // Probably do this by implementing a "cache".
                return;
            }

            base.RunRequestAsync();
        }

        [HttpHeader("Authorization")]
        public string Authorization
        {
            get
            {
                return "Basic " + System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(Server.Username + ":" + Server.Password));
            }
        }
    }

    public class SubsonicPerSessionCacheRequest : SubsonicRequest
    {
        [QueryParam("nocache", FormatProviderType = typeof(GuidStorageNameFormatter))]
        public Guid NoCache
        {
            get
            {
                return ApplicationRunId.Instance.Value;
            }
        }
    }

    public class SubsonicBypassCacheRequest : SubsonicRequest
    {
        [QueryParam("nocache", FormatProviderType = typeof(GuidStorageNameFormatter))]
        public Guid NoCache
        {
            get
            {
                return Guid.NewGuid();
            }
        }
    }

    public class PingRequest : SubsonicBypassCacheRequest
    {
        public PingRequest()
        {
            Path = "ping.view";
        }
    }

    public class GetLicenseRequest : SubsonicPerSessionCacheRequest
    {
        public GetLicenseRequest()
        {
            Path = "getLicense.view";
        }
    }

    [Cacheable("Local")]
    public class GetMusicFoldersRequest : SubsonicPerSessionCacheRequest
    {
        public GetMusicFoldersRequest()
        {
            Path = "getMusicFolders.view";
        }
    }

    public class GetNowPlayingRequest : SubsonicBypassCacheRequest
    {
        public GetNowPlayingRequest()
        {
            Path = "getNowPlaying.view";
        }
    }

    [Cacheable("ApplicationState")]
    public class GetIndexesRequest : SubsonicPerSessionCacheRequest
    {
        public GetIndexesRequest()
        {
            Path = "getIndexes.view";
        }

        [QueryParam("musicFolderId")]
        public string MusicFolderId { get; set; }
        [QueryParam("ifModifiedSince")]
        public int? IfModifiedSince { get; set; }
    }

    [Cacheable("Local")]
    public class GetMusicDirectoryRequest : SubsonicPerSessionCacheRequest
    {
        public GetMusicDirectoryRequest(string id)
        {
            Path = "getMusicDirectory.view";
            Id = id;
        }

        [QueryParam("id", Required = true)]
        public string Id { get; set; }
    }

    public class SearchRequest : SubsonicBypassCacheRequest
    {
        public SearchRequest()
        {
            Path = "search.view";
        }

        [QueryParam("artist")]
        public string Artist { get; set; } // Artist to search for. 
        [QueryParam("album")]
        public string Album { get; set; } // Album to searh for. 
        [QueryParam("tile")]
        public string Title { get; set; } // Song title to search for. 
        [QueryParam("any")]
        public string Any { get; set; } // Searches all fields. 
        [QueryParam("count")]
        public int Count { get; set; } // Maximum number of results to return (20 max). 
        [QueryParam("offset")]
        public int Offset { get; set; } // Search result offset. Used for paging. 
        [QueryParam("newerThan")]
        public DateTime NewerThan { get; set; }  // Only return matches that are newer than this. Given as milliseconds since 1970.
    }

    public class GetPlaylistsRequest : SubsonicPerSessionCacheRequest
    {
        public GetPlaylistsRequest()
        {
            Path = "getPlaylists.view";
        }
    }

    public class GetPlaylistRequest : SubsonicPerSessionCacheRequest
    {
        public GetPlaylistRequest(string id)
        {
            Path = "getPlaylist.view";
            Id = id;
        }

        [QueryParam("id", Required = true)]
        public string Id { get; set; }
    }

    public class GuidStorageNameFormatter : CustomFormatProvider<Guid>
    {
        public GuidStorageNameFormatter()
            : base(g => GuidUtility.GuidToStorageName(g))
        {
        }
    }

    [Cacheable("ApplicationState")]
    public class GetAlbumListRequest : SubsonicPerSessionCacheRequest
    {
        public GetAlbumListRequest(AlbumListType albumListType)
        {
            Path = "getAlbumList.view";
            AlbumListType = albumListType;
        }


        protected override HttpWebRequest NewWebRequest()
        {
            HttpWebRequest req = base.NewWebRequest();
            req.Headers[HttpRequestHeader.CacheControl] = "no-cache";
            return req;
        }

        [QueryParam("type", Required = true)]
        public AlbumListType AlbumListType { get; set; }
        [QueryParam("size")]
        public int? Size { get; set; }
        [QueryParam("offset")]
        public int? Offset { get; set; }

    }


    public class GetRandomSongsRequest : SubsonicPerSessionCacheRequest
    {
        public GetRandomSongsRequest()
        {
            Path = "getRandomSongs.view";
        }

        [QueryParam("size")]
        public int? Size { get; set; }
        [QueryParam("genre")]
        public string Genre { get; set; }
        [QueryParam("fromYear")]
        public int? FromYear { get; set; }
        [QueryParam("toYear")]
        public int? ToYear { get; set; }
        [QueryParam("musicFolderId")]
        public string MusicFolderId { get; set; }
    }

    public class GetLyricsRequest : SubsonicRequest
    {
        public GetLyricsRequest()
        {
            Path = "getLyrics.view";
        }

        [QueryParam("artist")]
        public string Artist { get; set; }
        [QueryParam("title")]
        public string Title { get; set; }
    }

    public class JukeboxControlRequest : SubsonicBypassCacheRequest
    {
        public JukeboxControlRequest(JukeboxAction action)
        {
            Path = "jukeboxControl.view";
            Action = action;
        }

        public JukeboxControlRequest(JukeboxAction action, params string[] ids)
            : this(action)
        {
            Id = new List<string>();
            Id.AddRange(ids);
        }

        protected override HttpWebRequest NewWebRequest()
        {
            HttpWebRequest req = base.NewWebRequest();
            req.Headers[HttpRequestHeader.CacheControl] = "no-cache";
            return req;
        }

        [QueryParam("action", Required = true)]
        public JukeboxAction Action { get; set; }
        [QueryParam("index")]
        public int? Index { get; set; }
        [QueryParamList("id")]
        public List<string> Id { get; set; }
        [QueryParam("gain")]
        public float? Gain { get; set; }
    }

}